﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Windows;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;

namespace Dummy
{
    public class GameClient
    {
        Socket _socket;
        SocketAsyncEventArgs _sendArgs;
        ManualResetEvent _clientWriteFlag = new ManualResetEvent(true);
        string dataBuffer = null;
        object _gameService;
        Encoding _encoding = Encoding.UTF8;

        public GameClient(object service)
        {
            _gameService = service;

            DnsEndPoint endPoint = new DnsEndPoint(Application.Current.Host.Source.DnsSafeHost, 4530);
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            _sendArgs = new SocketAsyncEventArgs();
            _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketSend);

            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.SocketClientAccessPolicyProtocol = SocketClientAccessPolicyProtocol.Tcp;
            //args.UserToken = _socket;
            args.RemoteEndPoint = endPoint;
            args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);
            _socket.ConnectAsync(args);            
        }

        public void StopClient()
        {
            _socket.Close();
        }

        void sendData(string msg)
        {
            _clientWriteFlag.WaitOne();
            _clientWriteFlag.Reset();
            try
            {
                byte[] bytes = _encoding.GetBytes(msg);
                _sendArgs.SetBuffer(bytes, 0, bytes.Length);
            }
            catch (Exception ex)
            {
            }
            _socket.SendAsync(_sendArgs);
        }

        void OnSocketSend(object sender, SocketAsyncEventArgs e)
        {
            _clientWriteFlag.Set(); //release write flag
            //check last send error
            SocketError se = e.SocketError;
        }

        private void OnSocketConnectCompleted(object sender, SocketAsyncEventArgs e)
        {
            byte[] response = new byte[1024];
            e.SetBuffer(response, 0, response.Length);
            e.Completed -= new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);
            e.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketReceive);
            //Socket socket = (Socket)e.UserToken;
            //socket.ReceiveAsync(e);
            _socket.ReceiveAsync(e);

            //debug
            //sendServiceClientCommand("connectgamer", "gnomick");
        }

        private void OnSocketReceive(object sender, SocketAsyncEventArgs e)
        {
            try
            {
                string newdata = _encoding.GetString(e.Buffer, e.Offset, e.BytesTransferred);
                processCommand(newdata);
            }
            catch { }
            //Prepare to receive more data
            //Socket socket = (Socket)e.UserToken;
            if (_socket.Connected)
                _socket.ReceiveAsync(e);
        }

        private void processCommand(string newdata)
        {
            dataBuffer += newdata;
            if (newdata.Contains("\r\n"))
            {
                if (dataBuffer.StartsWith("close"))
                    this.StopClient();
                else
                {
                    if (!invokeServiceCommand(dataBuffer))
                        //sendData("unknown command\r\n");
                        sendServiceClientCommand(GameClientCommand.error, "unknown command");
                    dataBuffer = "";
                }
            }
        }

        public void sendServiceClientCommand(GameClientCommand name, params string[] parameters)
        {
            string command = GameClient.GetCallbackCommand(name, parameters);
            sendData(command);
        }
        
        public static string GetCallbackCommand(GameClientCommand name, params string[] parameters)
        {
            return Enum.GetName(typeof(GameClientCommand), name) + ";" + string.Join(",", parameters) + "\r\n";
        }

        public static string GetCallbackParameter(string[] parameter)
        {
            return string.Join(":", parameter);
        }

        private bool invokeServiceCommand(string command)
        {
            try
            {
                if (_gameService == null)
                    throw new Exception();

                command = command.Replace("\r\n", "").Replace("\b", "").Trim();
                string[] cm = command.Split(';');
                if (cm.Length == 0)
                    throw new Exception();

                MethodInfo mi = _gameService.GetType().GetMethod(cm[0]);
                if ((mi == null) || (!mi.IsPublic))
                    throw new Exception();

                string[] psplit = command.EndsWith(";") ? new string[0] : cm[1].Split(',');
                object[] parameters = getMethodParams(psplit, mi.GetParameters());
                if (parameters == null)
                    throw new Exception();

                object res = mi.Invoke(_gameService, parameters);
                return true;
            }
            catch (Exception ex)
            {
                invokeErrorCommand(command);
                return false;
            }
        }

        private void invokeErrorCommand(string wrongcommand)
        {
            _gameService.GetType().GetMethod("errorcommand").Invoke(_gameService, new object[] { wrongcommand });
        }

        private object[] getMethodParams(string[] prms, ParameterInfo[] ps)
        {
            if (prms.Length != ps.Length - 1)
                return null;
            List<object> prmsO = new List<object>();
            prmsO.Add(new GameClientCallback(this.sendServiceClientCommand)); //callback is always first parameter
            for (int i = 1; i < ps.Length; i++)
            {
                if (ps[i].ParameterType == typeof(string))
                {
                    prmsO.Add(prms[i-1]);
                }
                else if (ps[i].ParameterType == typeof(int))
                {
                    int p;
                    if (!int.TryParse(prms[i-1], out p))
                        return null;
                    prmsO.Add(p);
                }
                else if (ps[i].ParameterType == typeof(bool))
                {
                    if ((prms[i-1].ToLower() != "true") && (prms[i-1].ToLower() != "false"))
                        return null;
                    prmsO.Add(prms[i-1].ToLower() == "true");
                }
                else if (ps[i].ParameterType == typeof(string[]))
                {
                    prmsO.Add(prms[i - 1].Split(':'));
                }
                else
                    return null;
            }
            return prmsO.ToArray();
        }
    }

    public delegate void GameClientCallback(GameClientCommand name, params string[] parameters);
}
